/*******************************************************************************
 * Copyright (c) 2014, 2015 IBM Corp.
 *
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * and Eclipse Distribution License v1.0 which accompany this distribution.
 *
 * The Eclipse Public License is available at
 *    http://www.eclipse.org/legal/epl-v10.html
 * and the Eclipse Distribution License is available at
 *   http://www.eclipse.org/org/documents/edl-v10.php.
 *
 * Contributors:
 *    Allan Stockdill-Mander - initial API and implementation and/or initial documentation
 *    Ian Craggs - convert to FreeRTOS
 *******************************************************************************/

#include "MQTTFreeRTOS.h"

int ThreadStart(Thread *thread, void (*fn)(void *), void *arg)
{
    int rc = 0;
    uint16_t usTaskStackSize = (configMINIMAL_STACK_SIZE * 5);
    UBaseType_t uxTaskPriority = uxTaskPriorityGet(NULL); /* set the priority as the same as the calling task*/

    rc = xTaskCreate(fn,              /* The function that implements the task. */
                     "MQTTTask",      /* Just a text name for the task to aid debugging. */
                     usTaskStackSize, /* The stack size is defined in FreeRTOSIPConfig.h. */
                     arg,             /* The task parameter, not used in this case. */
                     uxTaskPriority,  /* The priority assigned to the task is defined in FreeRTOSConfig.h. */
                     &thread->task);  /* The task handle is not used. */

    return rc;
}

void MutexInit(Mutex *mutex)
{
    mutex->sem = xSemaphoreCreateMutex();
}

int MutexLock(Mutex *mutex)
{
    return xSemaphoreTake(mutex->sem, portMAX_DELAY);
}

int MutexUnlock(Mutex *mutex)
{
    return xSemaphoreGive(mutex->sem);
}

void TimerCountdownMS(Timer *timer, unsigned int timeout_ms)
{
    timer->xTicksToWait = timeout_ms / portTICK_PERIOD_MS; /* convert milliseconds to ticks */
    vTaskSetTimeOutState(&timer->xTimeOut);                /* Record the time at which this function was entered. */
}

void TimerCountdown(Timer *timer, unsigned int timeout)
{
    TimerCountdownMS(timer, timeout * 1000);
}

int TimerLeftMS(Timer *timer)
{
    xTaskCheckForTimeOut(&timer->xTimeOut, &timer->xTicksToWait); /* updates xTicksToWait to the number left */
    return (timer->xTicksToWait < 0) ? 0 : (timer->xTicksToWait * portTICK_PERIOD_MS);
}

char TimerIsExpired(Timer *timer)
{
    return xTaskCheckForTimeOut(&timer->xTimeOut, &timer->xTicksToWait) == pdTRUE;
}

void TimerInit(Timer *timer)
{
    timer->xTicksToWait = 0;
    memset(&timer->xTimeOut, '\0', sizeof(timer->xTimeOut));
}

int FreeRTOS_read(Network *n, unsigned char *buffer, int len, int timeout_ms)
{
    TickType_t xTicksToWait = timeout_ms / portTICK_PERIOD_MS; /* convert milliseconds to ticks */
    struct timeval send_timeout;
    TimeOut_t xTimeOut;
    int recvLen = 0;

    int rc = 0;

    send_timeout.tv_sec = 0;
    send_timeout.tv_usec = 1000 * xTicksToWait;

    rc = setsockopt(n->my_socket, SOL_SOCKET, SO_RCVTIMEO, &send_timeout, sizeof(send_timeout));
    if (rc < 0) {
        printf("Setsockopt for recv timeout failed  rc value is %d\r\n", rc);
    }

    vTaskSetTimeOutState(&xTimeOut); /* Record the time at which this function was entered. */
    do {
        rc = recv(n->my_socket, buffer + recvLen, len - recvLen, 0);

        if (rc == -1) {
            if (errno != EAGAIN && errno != EWOULDBLOCK)
                recvLen = -1;
            break;
        } else if (rc == 0) {
            recvLen = 0;
            break;
        } else
            recvLen += rc;
    } while (recvLen < len && xTaskCheckForTimeOut(&xTimeOut, &xTicksToWait) == pdFALSE);

    return recvLen;
}

int FreeRTOS_write(Network *n, unsigned char *buffer, int len, int timeout_ms)
{
    TickType_t xTicksToWait = timeout_ms / portTICK_PERIOD_MS; /* convert milliseconds to ticks */
    struct timeval send_timeout;
    TimeOut_t xTimeOut;
    int sentLen = 0;

    send_timeout.tv_sec = 0;
    send_timeout.tv_usec = 1000 * xTicksToWait;

    if (setsockopt(n->my_socket, SOL_SOCKET, SO_SNDTIMEO, (char *)&send_timeout, sizeof(send_timeout)) < 0) {
        printf("Setsockopt for send timeout failed\r\n");
    }

    vTaskSetTimeOutState(&xTimeOut); /* Record the time at which this function was entered. */
    do {
        int rc = 0;
        // rc = send(n->my_socket, buffer + sentLen, len - sentLen, 0);
        rc = write(n->my_socket, buffer + sentLen, len - sentLen);

        if (rc > 0)
            sentLen += rc;
        else if (rc < 0) {
            sentLen = rc;
            break;
        }
    } while (sentLen < len && xTaskCheckForTimeOut(&xTimeOut, &xTicksToWait) == pdFALSE);

    return sentLen;
}

void FreeRTOS_disconnect(Network *n)
{
    closesocket(n->my_socket);
}

void NetworkInit(Network *n)
{
    n->my_socket = 0;
    n->mqttread = FreeRTOS_read;
    n->mqttwrite = FreeRTOS_write;
    n->disconnect = FreeRTOS_disconnect;
}

int NetworkConnect(Network *n, char *addr, int port)
{
    //     struct sockaddr_in sAddr;
    //     int retVal = -1;

    //     struct hostent *ipAddress;

    //     if ((ipAddress = gethostbyname(addr)) == 0) {
    //         goto exit;
    //     }

    //     sAddr.sin_family = AF_INET;
    //     sAddr.sin_port = htons(port);
    //     // sAddr.sin_addr.s_addr = inet_addr("110.41.10.116");

    //     printf("addr:%s\r\n", ipAddress->h_addr);

    //     memcpy(&sAddr.sin_addr.s_addr, ipAddress->h_addr, ipAddress->h_length);

    //     if ((n->my_socket = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
    //         printf("socket create fail!\r\n");
    //         goto exit;
    //     }

    //     // FD_ZERO(&n->read_fds);
    //     // FD_SET(n->my_socket, &n->read_fds);

    //     if ((retVal = connect(n->my_socket, &sAddr, sizeof(sAddr))) < 0) {
    //         printf("connect create fail!\r\n");
    //         closesocket(n->my_socket);
    //         goto exit;
    //     }

    // exit:
    //     return retVal;

    int type = SOCK_STREAM;
    struct sockaddr_in address;
    int rc = -1;
    sa_family_t family = AF_INET;
    struct addrinfo *result = NULL;
    struct addrinfo hints = { 0, AF_UNSPEC, SOCK_STREAM, IPPROTO_TCP, 0, NULL, NULL, NULL };

    if ((rc = getaddrinfo(addr, NULL, &hints, &result)) == 0) {
        struct addrinfo *res = result;

        /* prefer ip4 addresses */
        while (res) {
            if (res->ai_family == AF_INET) {
                result = res;
                break;
            }
            res = res->ai_next;
        }

        if (result->ai_family == AF_INET) {
            address.sin_port = htons(port);
            address.sin_family = family = AF_INET;
            address.sin_addr = ((struct sockaddr_in *)(result->ai_addr))->sin_addr;

            char ipAddress[128];
            inet_ntop(AF_INET, &(address.sin_addr), ipAddress, INET_ADDRSTRLEN);

            printf("The IP address is: %s\n", ipAddress);
        } else
            rc = -1;

        freeaddrinfo(result);
    }

    if (rc == 0) {
        n->my_socket = socket(family, type, 0);
        if (n->my_socket != -1)
            rc = connect(n->my_socket, (struct sockaddr *)&address, sizeof(address));
        else
            rc = -1;
    }

    return rc;
}

#if 0
int NetworkConnectTLS(Network *n, char* addr, int port, SlSockSecureFiles_t* certificates, unsigned char sec_method, unsigned int cipher, char server_verify)
{
	SlSockAddrIn_t sAddr;
	int addrSize;
	int retVal;
	unsigned long ipAddress;

	retVal = sl_NetAppDnsGetHostByName(addr, strlen(addr), &ipAddress, AF_INET);
	if (retVal < 0) {
		return -1;
	}

	sAddr.sin_family = AF_INET;
	sAddr.sin_port = sl_Htons((unsigned short)port);
	sAddr.sin_addr.s_addr = sl_Htonl(ipAddress);

	addrSize = sizeof(SlSockAddrIn_t);

	n->my_socket = sl_Socket(SL_AF_INET, SL_SOCK_STREAM, SL_SEC_SOCKET);
	if (n->my_socket < 0) {
		return -1;
	}

	SlSockSecureMethod method;
	method.secureMethod = sec_method;
	retVal = sl_SetSockOpt(n->my_socket, SL_SOL_SOCKET, SL_SO_SECMETHOD, &method, sizeof(method));
	if (retVal < 0) {
		return retVal;
	}

	SlSockSecureMask mask;
	mask.secureMask = cipher;
	retVal = sl_SetSockOpt(n->my_socket, SL_SOL_SOCKET, SL_SO_SECURE_MASK, &mask, sizeof(mask));
	if (retVal < 0) {
		return retVal;
	}

	if (certificates != NULL) {
		retVal = sl_SetSockOpt(n->my_socket, SL_SOL_SOCKET, SL_SO_SECURE_FILES, certificates->secureFiles, sizeof(SlSockSecureFiles_t));
		if (retVal < 0)
		{
			return retVal;
		}
	}

	retVal = sl_Connect(n->my_socket, (SlSockAddr_t *)&sAddr, addrSize);
	if (retVal < 0) {
		if (server_verify || retVal != -453) {
			sl_Close(n->my_socket);
			return retVal;
		}
	}

	SysTickIntRegister(SysTickIntHandler);
	SysTickPeriodSet(80000);
	SysTickEnable();

	return retVal;
}
#endif
